#ifndef SCROLLBAR_H
#define SCROLLBAR_H

#include "widget.h"
#include "../../anime/utils.h"

#include <QList>

class ScrollBar;

class ScrollBarListener
{
public:
    virtual void OnPositionChanged(ScrollBar* scrollbar, int newPos) = 0;
};


class ScrollBar: public Widget
{
public:
    enum ScrollOrientation {Vertical, Horizontal};

    ScrollBar(Widget* _parent, Engine* _engine, int _x, int _y, int _w, int _h,
              ScrollOrientation _orientation, int viewportSize, int windowSize)
                  :Widget(_parent, _engine, _x, _y, _w, _h),
                  orientation(_orientation),
                  viewport(viewportSize),
                  window(windowSize),
                  transparentBrush(Qt::NoBrush),
                  blackPen(QColor(0, 0, 0, 255)),
                  scrollBrush(QColor(69, 27, 139, 150), Qt::SolidPattern),
                  transparentPen(QColor(0, 0, 0, 0)),
                  arrowsBackBrush(transparentBrush),
                  backScrollBrush(QColor(148, 191, 207), Qt::Dense6Pattern),
                  arrowsBrush(scrollBrush),
                  backLayer(w + 1, h + 1),
                  scrollAnime(_engine->fps, 0)
    {
        backLayer.fill(QColor(255, 255, 255, 0));
        DrawBack();

        windowPos = 0;
        scrollOffset = 0;
        scrollLength = -1;
        scrolling = false;
        UpdateScrollLength();

        wPosGetter = new SimplePropertyGetter<int>(&windowPos);
        wPosSetter = new WindowPosSetter(this, &windowPos, &listeners);
        animeGraph.AddProperty("wPos", wPosGetter, wPosSetter);
        windowPosId = animeGraph.AddInterpolator(scrollAnime.GetTime(), 0, 0, "wPos", 0, 0);
        engine->AddAnimation(&scrollAnime);
        UpdateScrollAnimationParameters();

        mouseTracking = true;
    }

    virtual ~ScrollBar()
    {
        delete wPosGetter;
        delete wPosSetter;
    }

    // window and viewport handling
    virtual int GetViewportSize() { return viewport; }
    virtual int GetWindowSize() { return window; }

    virtual void SetWindowSize(int newWindow)
    {
        window = newWindow;
        Reset();
    }

    virtual void Reset()
    {
        windowPos = 0;
        scrollOffset = 0;
        UpdateScrollLength();
        UpdateScrollAnimationParameters();
        NotifyListeners();
    }

    // paint function
    virtual void Paint(QPainter &painter)
    {
        // paints the background
        painter.drawPixmap(x, y, backLayer, 0, 0, w + 1, h + 1);

        // paints the scroll
        if(scrollLength != -1)
        {
            UpdateScrollOffset();
            painter.setPen(blackPen);
            painter.setBrush(scrollBrush);

            if(orientation == Vertical)
            {
                painter.drawRect(x, y + w + scrollOffset, w, scrollLength);
            }else
            {
                painter.drawRect(x + h + scrollOffset, y, scrollLength, h);
            }
        }

        DrawArrows(painter);
    }

    // position handling
    virtual int GetPosition()
    {
        return windowPos;
    }

    virtual void SetPosition(int newPos)
    {
        if(newPos >=0 && newPos <=window)
        {
            windowPos = newPos;
            NotifyListeners();
        }
    }

    // listeners managing
    virtual void AddListener(ScrollBarListener*  listener)
    {
        listeners.append(listener);
    }

    virtual void ClearListeners()
    {
        listeners.clear();
    }

    virtual void RemoveListener(ScrollBarListener* listener)
    {
        listeners.removeOne(listener);
    }

    // mouse handling
    virtual void OnMousePressed(QMouseEvent *e, bool inside, int x_rel, int y_rel)
    {
        if(inside && (window > viewport))
        {
            if(ArrowsPickCorrelation(x_rel, y_rel)) return;
            if(ScrollPickCorrelation(x_rel, y_rel)) return;
            if(BackScrollPickCorrelation(x_rel, y_rel)) return;
        }
    }

    virtual void OnMouseReleased(QMouseEvent *e, bool inside, int x_rel, int y_rel)
    {
        scrolling = false;
        scrollAnime.Pause();
    }

    virtual void OnMouseMoved(QMouseEvent *e, bool onWidget, int x_rel, int y_rel)
    {
        if(scrolling)
        {
            float dBar = ((orientation == Vertical)? y_rel : x_rel) - oldCoord;
            int dw = dBar/barLength * window;
            int newWindowPos = oldWindowPos + dw;
            if(!(newWindowPos < 0 || newWindowPos + viewport > window))
            {
                if(newWindowPos < 0) newWindowPos = 0;
                if(newWindowPos + viewport > window) newWindowPos = window - viewport;
                windowPos = newWindowPos;
                NotifyListeners();
            }
        }
    }

private:
    class WindowPosSetter: public PropertySetter<int>
    {
    public:
        WindowPosSetter(ScrollBar* _owner, int* _wPosPointer, QList< ScrollBarListener* >* _l)
        {
            owner = _owner;
            wPosPointer =  _wPosPointer;
            l = _l;
        }

        virtual void operator()(const int& val)
        {
            if (val != *wPosPointer)
            {
                *wPosPointer = val;

                for(int i=0; i < l->size(); i++)
                {
                    l->at(i)->OnPositionChanged(owner, val);
                }
            }
        }

        ScrollBar* owner;
        int* wPosPointer;
        QList< ScrollBarListener* >* l;
    };

    ScrollOrientation orientation;

    int viewport;
    int window;
    int windowPos;

    int scrollLength;
    int barLength;
    int scrollOffset;

    int oldWindowPos;
    int oldCoord;
    bool scrolling;

    QBrush transparentBrush;
    QPen blackPen;
    QBrush scrollBrush;
    QPen transparentPen;
    QBrush arrowsBackBrush;
    QBrush backScrollBrush;
    QBrush arrowsBrush;

    QPixmap backLayer;

    QList< ScrollBarListener* > listeners;

    Animation scrollAnime;
    int windowPosId;
    IntGraph animeGraph;
    SimplePropertyGetter<int>* wPosGetter;
    WindowPosSetter* wPosSetter;

    void DrawBack()
    {
        QPainter p(&backLayer);
        p.setPen(blackPen);
        p.setBrush(transparentBrush);
        p.drawRect(0, 0, w, h);

        if(orientation == Horizontal)
        {
            p.drawLine(h, 0, h, h);
            p.drawLine(w - h, 0, w - h, h);

            p.setPen(transparentPen);
            p.setBrush(backScrollBrush);
            p.drawRect(h + 1, 1, w - 2 * h - 1, h - 1);

            p.setBrush(arrowsBackBrush);
            p.drawRect(0, 0, h, h);
            p.drawRect(w - h, 0, h, h);
        }else
        {
            p.drawLine(0, w, w, w);
            p.drawLine(0, h - w, w, h - w);

            p.setPen(transparentPen);
            p.setBrush(backScrollBrush);
            p.drawRect(0, w + 1, w, h - 2 * w - 1);

            p.setBrush(arrowsBackBrush);
            p.drawRect(0, 0, w, w);
            p.drawRect(0, h - w, w, w);
        }
    }

    void UpdateScrollLength()
    {
        barLength = (orientation == Vertical)? (h - 2 * w) : (w - 2 * h);
        if(viewport < window)
        {
            float f = (float)viewport / window;
            scrollLength = f * barLength;
        }else
        {
            scrollLength = -1;
        }
    }

    void UpdateScrollOffset()
    {
        scrollOffset = ((float)windowPos / window) * barLength;
    }

    void DrawArrows(QPainter& painter)
    {
        painter.setPen(blackPen);
        painter.setBrush(arrowsBrush);

        if(orientation == Vertical)
        {
            QPoint points[3] = {
                QPoint(x + w/4, y + w - w/4),
                QPoint(x + w/2, y + w/4),
                QPoint(x + w - w/4, y + w - w/4)
            };
            painter.drawPolygon(points, 3);

            points[0] = QPoint(x + w/4, y + h - w + w/4);
            points[1] = QPoint(x + w/2, y + h - w/4);
            points[2] = QPoint(x + w - w/4, points[0].y());
            painter.drawPolygon(points, 3);
        } else
        {
            QPoint points[3] = {
                QPoint(x + h - h/4, y + h/4),
                QPoint(x + h/4, y + h/2),
                QPoint(x + h - h/4, y + h - h/4)
            };
            painter.drawPolygon(points, 3);

            points[0] = QPoint(x + w - h + h/4, y + h/4);
            points[1] = QPoint(x + w - h/4, y + h/2);
            points[2] = QPoint(x + w - h + h/4, y + h - h/4);
            painter.drawPolygon(points, 3);
        }
    }

    // pick correlation functions
    bool ArrowsPickCorrelation(int x_rel, int y_rel)
    {
        QRect firstArrowRect = (orientation == Vertical)? QRect(0, 0, w, w) : QRect(0, 0, h, h);
        QRect secondArrowRect = (orientation == Vertical)? QRect(0, h - w, h, h) : QRect(w - h, 0, h, h);

        if(firstArrowRect.contains(x_rel, y_rel, true))
        {
            RecalculateAnimationTime();
            scrollAnime.Reverse(true);
            scrollAnime.Play();
            return true;
        }

        if(secondArrowRect.contains(x_rel, y_rel, true))
        {
            RecalculateAnimationTime();
            scrollAnime.Reverse(false);
            scrollAnime.Play();
            return true;
        }
        return false;
    }

    bool ScrollPickCorrelation(int x_rel, int y_rel)
    {
        if(window > viewport)
        {
            QRect scrollRect;
            if(orientation == Vertical)
            {
                scrollRect = QRect(0, w + scrollOffset, w, scrollLength);
            }else
            {
                scrollRect = QRect(h + scrollOffset, 0, scrollLength, h);
            }

            if(scrollRect.contains(x_rel, y_rel, true))
            {
                oldWindowPos = windowPos;
                oldCoord = (orientation == Vertical)? y_rel : x_rel;
                scrolling = true;
                return true;
            }
        }
        return false;
    }

    bool BackScrollPickCorrelation(int x_rel, int y_rel)
    {
        QRect scrollRect = (orientation == Vertical)? QRect(0, w + scrollOffset, w, scrollLength) :
                                                      QRect(h + scrollOffset, 0, scrollLength, h);
        QRect backRect = (orientation == Vertical)? QRect(0, w, w, h - 2 * w) : QRect(h, 0, w - 2 * h, h);

        if(backRect.contains(x_rel, y_rel, true) && !scrollRect.contains(x_rel, y_rel))
        {
            int barPos = ((orientation == Vertical)? y_rel : x_rel) - scrollLength/2 - ((orientation == Vertical)? w : h);
            if(barPos + scrollLength > barLength)
            {
                barPos = barLength - scrollLength;
            }
            if(barPos < 0) barPos = 0;

            windowPos = (barPos / (float)barLength) * window;
            NotifyListeners();
            return true;
        }
        return false;
    }

    void UpdateScrollAnimationParameters()
    {
        int invisibleWindow = window - viewport;
        if(invisibleWindow > 0)
        {
            scrollAnime.SetDuration(invisibleWindow * 6);
            animeGraph.Get(windowPosId)->SetMaxY(invisibleWindow);
        }
    }

    void RecalculateAnimationTime()
    {
        scrollAnime.SetCurrentTime(windowPos * 6);
    }

    inline void NotifyListeners()
    {
        for(int i=0; i < listeners.size(); i++)
        {
            listeners.at(i)->OnPositionChanged(this, windowPos);
        }
    }
};

#endif // SCROLLBAR_H
